/**
  需求：点击绑定元素之外时调用传递的binding.value。
 */
import { ObjectDirective } from 'vue';
import { on } from '../utils/dom';

// 存储绑定此指令的Dom列表
const ctx = '@@clickOutsideContext';
const nodeList: any[] = [];
let seed = 0;

// 得到Event
let startClick: MouseEvent;
on && on(document, 'mousedown', (e: MouseEvent) => (startClick = e));
on && on(document, 'mouseup', (e: MouseEvent) => {
  nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
});

// 判断event.target, 执行绑定事件
function createDocumentHandler(el: any, binding: any, vnode: any) {
  return function (mouseup: MouseEvent, mousedown: MouseEvent) {
    if (
      !vnode ||
      !vnode.el ||
      !mouseup.target ||
      !mousedown.target ||
      el === mouseup.target ||
      el.contains(mouseup.target) ||
      el.contains(mousedown.target) ||
      (vnode.el &&
        (vnode.el.contains(mouseup.target) ||
          vnode.el.contains(mousedown.target)))
    ) return;

    if (el[ctx].methodName && vnode.el[ctx].methodName) {
      vnode.el[ctx].bindingFn();
    } else {
      el[ctx].bindingFn && el[ctx].bindingFn();
    }
  };
}

/**
 * @description 正则匹配以[开头以]结尾不包含[]: str.match(/(?<=\[).*?(?=\])/g)
 * @description 正则匹配以[开头以]结尾包含[]: str.match(/\[.*?\]/g)
 * @description 正则匹配 {} 以及大括号 中间的内容: str.match(/\{[^\{]+\}/g)
 */
const clickOutside: ObjectDirective<any, string> = {
  created(el, binding, vnode) {
    const aMethodName = binding.value.toString().match(/(?<=function).*?(?=\()/g);
    el.methodName = undefined;
    if (aMethodName && aMethodName.length > 0) {
      el.methodName = aMethodName[0].trim()
    }
    nodeList.push(el);
    const id = seed++;
    el[ctx] = {
      id,
      documentHandler: createDocumentHandler(el, binding, vnode),
      methodName: el.methodName,
      bindingFn: binding.value
    };
  },
  updated(el, binding, vnode) {
    el[ctx].documentHandler = createDocumentHandler(el, binding, vnode);
    el[ctx].methodName = el.methodName;
    el[ctx].bindingFn = binding.value;
  },
  beforeUnmount(el) {
    let len = nodeList.length;
    for (let i = 0; i < len; i++) {
      if (nodeList[i][ctx].id === el[ctx].id) {
        nodeList.splice(i, 1);
        break;
      }
    }
    delete el[ctx];
  }
}

export default clickOutside;
